Facebook 在 2012 年開始在公司內部使用 GraphQL,而在 2015 年 7 月開源並撰寫了正式的規格,開源至今還不算很久,但已經引起了廣泛的討論也有了越來越多的使用者。雖然 GraphQL 很新很潮,但我們是為了解決問題,而不是為了使用新技術而使用新技術,所以在這篇之中會特別說明 REST 的缺點,以及 GraphQL 如何解決這些問題。
GraphQL 最初是從 Facebook 想要優化手機體驗開始發展而來的查詢語言,要理解它可以先把它想成是一個沒有值的 JSON。
下面這個查詢:
{
me {
id
name
}
}
可以拿回以下格式的結果:
{
"me": {
"id": "chentsulin",
"name": "C. T. Lin"
}
}
至於更多的細節會在這篇以及接下來的篇章慢慢提到。
過去十年間,HTTP API 介面設計幾乎為因 Rails 而廣泛為人所知的 REST 所主宰。
REST 利用 GET
、POST
、PUT
、DELETE
等等的 HTTP 動詞,搭配 /resouces
、/resouces/:id
這樣代表資源集合以及個別資源的路徑來作為 API 介面,例如:
GET /users <- 取得使用者清單
POST /users <- 新增使用者
GET /users/1 <- 取得 id=1 的使用者
PUT /users/1 <- 修改 id=1 的使用者
DELETE /users/1 <- 刪除 id=1 的使用者
而回應會附帶著合適的 HTTP Status Code,可以清楚知道這次請求的結果,例如:
200 OK
400 Bad Request
404 Not Found
依照著這樣的慣例,開發者都可以很容易了解彼此的介面,設計一致的 API 也變得簡單許多。
雖然瑕不掩瑜,但 REST 終究不是只有美好的一面,有以下這些不易處理的問題:
會在下面一一提及:
HTTP API 傳參數最常見的格式是用 x-form-www-urlencoded
、json
或是 form-data
,回傳值則是 json
比較多見。
因為 JavaScript 是弱型別的語言,中間不管是在 HTTP 層或是在 Database 層,都有可能被進行了轉型,這還跟使用怎樣的套件或是框架有關。例如,經過了 x-form-www-urlencoded
後,根本分不清楚 id=1
的 1
是數字 1
還是字串 "1"
,如果開發文件也不寫清楚那真的會很頭大。
筆者以前還曾經在跟大陸的某家廠商合作時,發現它 API 回傳的 JSON 裡面有一個值是 "null"
,當時研究了許久發現不了 bug,最後也只能苦笑:
{
"data": {
"video": {
"link": "null"
}
}
}
在 GraphQL 裡面,必須仔細的定義型別,所有的值都必須通過 GraphQL 的 Validation,就不會出現這類的失誤。
在 React Conference 上有講者提到 Facebook 在全球有 96 個版本同時上線,這用想的就很令人崩潰。
而我們平常在寫網站服務時,最常見的前後端工程師之間的溝通也很類似:
前端工程師 A:為什麼那個欄位不見了?
後端工程師 B:啊,我把它改名了啊。
這樣的問題雖然細微,卻是很常發生而且致命的。
在 GraphQL 裡面前後端可以共用一個固定的 Schema,確保想要拿的資料能確實拿到。
我們用 REST 在拿資料時,通常是拿整個 Resource:
GET /posts/1
拿回:
{
"author": "chentsulin",
"title": "Day 18:GraphQL 入門 Part I - 從 REST 到 GraphQL",
"body": "前言....",
"comments": []
}
如果我只想要 title
,那其他的屬性就浪費了網路的傳輸量,雖然也可以加上 field
、ignore
之類參數的方式來限制欄位,但也不是標準的做法。
在 GraphQL 裡面,要查詢的欄位要清楚的列出來,其他欄位就不會包括在回應裡面:
{
post(id: "1") {
title
}
}
{
"post": {
"title": "Day 18:GraphQL 入門 Part I - 從 REST 到 GraphQL"
}
}
REST 的目標是去處理單一資源,處理資源之間的關聯就變成一個灰色地帶。例如,要拿回一篇文章以及它的作者,可能會這樣做:
GET /posts/1 <- 拿到 author id
GET /users/chentsulin
如果把 User 的資訊直接併入 Post 中,雖然可以減少一次的查詢,但又在某些不需要關聯的時候會多做資料庫的查詢。
GraphQL 則是這樣做的,要巢狀幾層都不是問題:
{
post(id: "1") {
title
author {
id
name
posts {
title
}
}
}
}
正規化通常總是不利於效能的,如果想要一次多拿一些東西,避免網路上的往返損耗,可能會長出一堆變種的 Endpoint,例如:
GET /post-with-comments
GET /post-with-comments-and-replies
GET /post-with-comments-and-replies-and-likers
越來越多的 Endpoint 會導致複雜度跟維護難度漸漸增高,也破壞了 REST 原本的設計。
GraphQL 則永遠都只有一個 Endpoint,來處理所有的查詢。
直到 2015 年,GraphQL 已經可以在一天內為 Facebook 處理 2600 億 (260 Billion) 個網路請求,開源至今除了 Facebook,國外已經有 Twitter、Github、Pinterest、Coursera 等等許多公司採用,國內也有 Yoctol (筆者所待的公司)、Appier、Fandora 等等的公司在接觸、使用 GraphQL 的技術。筆者對於 GraphQL 的未來發展是相當樂觀的,尤其是在處理大量關聯資料時,它的優點非常顯而易見,一但用了就很難回去了。
以下是 GraphQL 入門系列的目錄: